#Copyright Ken Faulkner 2007.
import threading
import Queue
import sys
import urllib
import os
import os.path
import subprocess
import stat
import time
from Common.logRoutine import *
from Common.MiscConst import *

import traceback
from Framework.Converter.MpegEncoder import MpegEncoder
from Datatypes.AudioDetails import AudioDetails
from Commands import *
import random



class RealAudioDownloader(threading.Thread):
  """
  Although just initially a downloader, this could also do the encoding since we dont
  want TOOO many threads running about the place.
  Waddayathunk?
  
  Important Note:
  
  There are 3 Queues involved with this class.
  URLRequestQ -- This specifies the URL of a given item to download. This is shared for all threads.
  URLResponseQ -- This returns status information to the client about downloading. This is shared for all thread.s
  commandQ -- This allows the client to communicate with the thread while the download is actually happening.
              This is unique to this thread.
  """
  
  PERCENT_COMPLETE_TRIGGERS_STOP = 105.00
  
  def __init__(self, config, user_config, URLRequestQ, URLResponseQ, commandQ, **kwds):
    threading.Thread.__init__(self, **kwds)

    self.URLRequestQ = URLRequestQ
    self.URLResponseQ = URLResponseQ
    self.commandQ = commandQ
    self.config = config
    self.user_config = user_config
    
    # current item we're working on
    self.currentItem = None
    
    self.log = getLogger()
  
    self.encoder = MpegEncoder( config, user_config )

    self.downloadFile = None

    # setup a timer.
    self.timer = threading.Timer(5, self.__onTimer )
    self.timer.start()
    
    self.downloadThread = None

    self.mplayerPID = None
    
    # Whats the equiv in Windows?
    self.home_dir = os.path.join( os.getenv("HOME"), ".bbc" )
    self.cache_dir = os.path.join( self.home_dir, "cache" )
    self.log.debug("cache dir " + self.cache_dir)

    
  def __onTimer( self ):
    """
    Timer to check download progress of this file.
    Probably could just do with a single timer, but will have one per
    download. 
    """

    #self.log.info("RealAudioDownloader::onTimer start")
    try:

      if self.downloadFile != None:
      
        res = os.stat( self.downloadFile )

        currentSize = float( res[ stat.ST_SIZE ] )
        
        estimatedSize = float( self.currentItem.estimated_size )
        percentComplete = ( currentSize / estimatedSize ) * 100.0
        
        self.log.debug("checking size " + str( currentSize ) )
        self.log.debug("percent complete " + str( percentComplete  ) )
        
        self.log.debug("item status is " + str( self.currentItem.status ) )
        
        self.currentItem.status = AudioDetails.STATUS_DOWNLOADING
        self.currentItem.percent_complete = percentComplete
        self.URLResponseQ.put( self.currentItem )
    
    
        # ugly ugly, shouldn't do it this way.
        if percentComplete > RealAudioDownloader.PERCENT_COMPLETE_TRIGGERS_STOP:
          os.kill( self.mplayerPID, 9 )
          
    
    except:
      self.log.error("RealAudioDownloader::onTimer ex " + traceback.format_exc() )

    self.timer = threading.Timer(5, self.__onTimer )
    self.timer.start()

  def __getRAFile(self, url):
    """
    Download ram file and extract the rtsp url inside it.
    """
    self.log.info("RealAudioDownloader::getRAFile start")
    
    raURL = None
  
    try:
      
      data = urllib.urlopen( url ).read()
      
      sp = data.split("?")
      
      raURL = sp[0]
      
      sp2 = sp[1].split("&")
      if sp2[0].startswith("start"):
        raURL += "?" + sp2[0]
        
      self.log.debug("raURL is " + raURL )
      
    except:
      self.log.error("RealAudioDownloader::getRAFile ex " + traceback.format_exc() )
 
    return raURL
    

  def get_ra_link( self, url ):
    """
    Get the RealAudio link from the contents of the given url.
    
    Should do far better parsing.
    """
    self.log.info("LinkDownloader::get_ra_link start")

    ra_file = None
    
    try:
    
      main_data = urllib.urlopen( url ).read()
     
      idx = main_data.find("iplayer/aod")
      if idx != -1:
        ra_file = main_data[idx-30:].split('"')[1]
          
    except:
      self.log.error("LinkDownloader::get_ra_link ex " + traceback.format_exc() )

    return ra_file
          
      
  def __downloadURL(self, item):
    """
    Download the url in question.
    """
    self.log.info("RealAudioDownloader::downloadURL start")
    
    downloadFile = None
  
    try:
     
      self.currentItem = item
      
      
      url = self.get_ra_link( item.link )
      #url = item.link
      
      newURL = self.__getRAFile( url )
      
      # absolute path to cache dir.
      self.downloadFile = os.path.join( self.cache_dir,  str( time.time() ) + str( random.randrange(100) ) )
      
      # ugly hack to make sure names are unique.
      # and even then, its not a guarentee
      time.sleep(1)
      
      # escape spaces. Will this do for windows?
      self.downloadFile = self.downloadFile.replace(" ", "\ ")
      
      mplayerBinary = self.user_config['MPlayerBinary']
      
      # can I make mplayer dump a wav file?
      # I expect it's possible.
      command2 = "-bandwidth 9999999999 -noframedrop -dumpfile %s -dumpstream %s"%(  self.downloadFile, newURL)
      
      c2 = command2.split(" ")
    
      c3 = [mplayerBinary] + c2
      self.log.debug("mplayer c3 is " + str( c3 ) )
      
      p = subprocess.Popen(  c3, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
      
      self.mplayerPID = p.pid
      
      self.log.debug("waiting " + str( p.pid ) )
      
      ret = p.wait()
      
      self.log.debug("mplayer command %s returned %s"%(str(c3),  str( ret ) ) )
      
      # if successfully finished, or forced kill.. yeah yeah... then assume we're good.
      if ret == 0 or ret == -9: 
        # set the item status to downloaded.
        item.status = AudioDetails.STATUS_DOWNLOADED

        # we're finished, so issue quit command.
        self.commandQ.put( COMMAND_FINISH_DOWNLOAD )
            
      else:
        # set the item status to downloaded.
        item.status = AudioDetails.STATUS_ERROR

        # we're finished, so issue quit command.
        self.commandQ.put( COMMAND_ERROR_DOWNLOAD )
      
      #self.downloadFile = None
      
    except:
      self.log.error("RealAudioDownloader::downloadURL ex " + traceback.format_exc() )
 
  def __launchDownloadThread(self, item):
    """
    Download the url in question.
    """
    self.log.info("RealAudioDownloader::launchDownloadThread start")
    
    downloadFile = None
  
    try:

      self.downloadThread = threading.Thread( target = self.__downloadURL, args=( item, ) )
      self.downloadThread.start()
      
    except:
      self.log.error("RealAudioDownloader::launchDownloadThread ex " + traceback.format_exc() )
    
    
  def run(self):
    """
    Main thread loop
    
    This has to perform 2 tasks.
    
    Get commands from the URLRequestQ and act on them.
    Check status of item, and act on status changes.
    
    Ugly hack, but if this thread is already dealing with a download, and we
    end up getting another download command, then put the command back onto 
    the queue?
    
    """
    
    self.log.info("RealAudioDownloader::run start")
    
    try:
    
      request = {}
      
      # indicating if we're dealing with a download.
      amBusy = False
      
      # url we're dealing with.
      currentURL = None
      
      quit = False
      while not quit:
      
        # get the download request.
        request = self.URLRequestQ.get()
    
        command = request['COMMAND']
        item = request['ITEM']
        url = item.link

        self.__launchDownloadThread( item )
        item.status = AudioDetails.STATUS_START_DOWNLOAD
        item.thread_no = self.getName()
        
        self.URLResponseQ.put( item  )              
     
        # loop over command queue.
        while True:
        
          time.sleep( 1 )
          
          try:
            
            # get command.
            command = self.commandQ.get_nowait()
            self.log.debug("COMMAND " + str( command ) )
            
            # quit the app, so leave all loops.
            if command == COMMAND_QUIT:
              quit = True
              break
              
            # quit the app, so leave all loops.
            if command == COMMAND_CANCEL_DOWNLOAD:
              self.log.debug("COMMAND cancel download!!!")
              
              os.kill( self.mplayerPID, 9 )
              
              # ugly ugly ugly.
              # just here so the thread that is doing the download is most likely dead.
              # then we reassign status.
              time.sleep(2)
              
              item.status = AudioDetails.STATUS_NORMAL
              item.percent_complete = 0
              
              os.remove( self.downloadFile )
              self.downloadFile = None
              
              self.URLResponseQ.put( item )
              break
              
            # download finished, so leave command loop.
            if command == COMMAND_FINISH_DOWNLOAD:
              if item.status == AudioDetails.STATUS_DOWNLOADED:
              
                self.encoder.generateMpeg( self.downloadFile, item  )
                
                self.downloadFile = None

                item.status = AudioDetails.STATUS_COMPLETELY_FINISHED
                
                # returns finished status, the url we were passed, and the mp3 filename of
                # the results.
                self.URLResponseQ.put( item )

              break

            # download failed. So tell the UI that it failed. User can 
            # manually retry again.
            if command == COMMAND_ERROR_DOWNLOAD:
             
               # hacky....  but try and put this back on the queue.
               # infinite loop problem, but give it a go.
               # get the download request.

               # force a sleep... just incase.
               # hack hack hack
               time.sleep( 60 )
               self.URLRequestQ.put( request )

 
               # returns finished status, the url we were passed, and the mp3 filename of
               # the results.
               #self.URLResponseQ.put( item )

               break
              
              
          except Queue.Empty, e:
            # assuming get_nowait barfed.
            #self.log.debug("EMPTY COMMAND Q")
            pass
            
    except:
      self.log.error("RealAudioDownloader::run ex " + traceback.format_exc() )
      
